/**
 * Simulation of a single Web Server using queue model concepts and
 * the Simpack library. This model has just one facility with one
 * service center, that represents the Web Server, and just one queue.
 *
 * @author Lucas dos Santos Casagrande
 * @version 1.0 Created on 9 de Agosto de 2006, 15:30
 */

package queue_model;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.Random;

import simpackNew.Const;
import simpackNew.Sim;
import simpackNew.SimEvent;
import simpackNew.User;

public class QueueModel {

    SimEvent event; // Event Descriptor
    int tokenCount; // Used to Identify the requests and control the number of arrivals
    int server; // Server descriptor
    User[] users; // System's users

    int politics; // Type of schedule politics
    int preemptiveRequest; // Do or don't  preempt
    int simulNumber; // Simulation number
    int contractVariation;
    double[] contracts; // Contracted means per class, refering first to class A, then B, ...
    final int percentageClassA; // Percentage of Class A requests
    Random rn;

    double fulfilledContracts[];	// Percentage of fulfilled contract per user
    double latenciesBelowAverage[];	// Percentage of latencies of a user below the contracted average

    File outputDirectory;

    FileWriter writer[];
    PrintWriter output[];
    
    /**
     * @param politics - Type of schedule politics
     * @param sN - Simulation Number
     * @param percentageClassA - Percentage of Class A requests
     * @param contractVariation - Variation of the contract
     * @param contracts - Contracted means per class, refering first to class A, then B, ...
     * 
     * Obs.: The number of classes and contracts must be equal.
     * */
    public QueueModel(int politics, int preemptiveRequest, int sN, int percentageClassA,
	    int contractVariation, double... contracts) {
	this.politics = politics;
	this.preemptiveRequest = preemptiveRequest;
	this.simulNumber = sN;
	this.percentageClassA = percentageClassA;
	this.contractVariation = contractVariation;
	this.fulfilledContracts = new double[Const.N_USERS_A + Const.N_USERS_B];
	this.latenciesBelowAverage = new double[Const.N_USERS_A + Const.N_USERS_B];
	this.contracts = new double[contracts.length];
	System.arraycopy(contracts, 0, this.contracts, 0, contracts.length);
	rn = new Random();
	rn.setSeed((long) sN);
	setupEnviroment();
    }

    /**
     * Initializes the simulation environment, creates the
     * service center, event descriptor, classes of users,
     * and tries to open I/O streams.
     */
    void setupEnviroment() {

	Sim.init(0.0, Const.LINKED);
	server = Sim.create_facility("Server", Const.N_SERVERS);
	event = new SimEvent();
	createUsers();
	tokenCount = 0;

	openStreams();

	System.out.println("\nEnvironment configurated...");
    }

    void createUsers() {
	int index, total;
	if (contracts.length != Const.N_CLASSES) {
	    System.err
		    .println("Amount of contracts is incompatible with the amount of classes.");
	    System.exit(Const.CONTRACT_ERROR);
	}

	total = Const.N_USERS_A + Const.N_USERS_B;
	users = new User[total];
	for (index = 0; index < Const.N_USERS_A; index++)
	    users[index] = new User(Const.CLASSE_A, index,
		    contracts[Const.CLASSE_A]);
	for (; index < total; index++)
	    users[index] = new User(Const.CLASSE_B, index,
		    contracts[Const.CLASSE_B]);
    }

    /**
     * Opens I/O streams.
     * */
    void openStreams() {
	int index, total = Const.N_USERS_A + Const.N_USERS_B;
	String fileName[] = new String[total];

	writer = new FileWriter[total];
	output = new PrintWriter[total];

	try {
	    File dataDIR = new File(Const.DATA_DIR);
	    File simulationDIR = new File(dataDIR, Const.SIMULATION_DIR
		    + simulNumber);
	    File arrival_MeanDIR = new File(simulationDIR,
		    Const.ARRIVAL_MEAN_DIR + Const.ARRIVAL_TIME);
	    File percentageDIR = new File(arrival_MeanDIR, Const.PERCENTAGE_DIR
		    + percentageClassA);
	    
	    switch (politics) {
	    case Const.FIFO:
		for (index = 0; index < Const.N_USERS_A; index++) {
		    fileName[index] = "FIFO_ClassA_Mean_" + Const.ARRIVAL_TIME
			    + "_perc" + percentageClassA + "_user" + index
			    + ".txt";
		}
		for (; index < total; index++) {
		    fileName[index] = "FIFO_ClassB_Mean_" + Const.ARRIVAL_TIME
			    + "_perc" + percentageClassA + "_user"
			    + index + ".txt";
		}
		outputDirectory = new File(percentageDIR, "FIFO");
		break;
	    case Const.EBS:
		for (index = 0; index < Const.N_USERS_A; index++) {
		    fileName[index] = "EBS_ClassA_Mean_" + Const.ARRIVAL_TIME
			    + "_perc" + percentageClassA + "_contr"
			    + contractVariation + "_user" + index + ".txt";
		}
		for (; index < total; index++) {
		    fileName[index] = "EBs_ClassB_Mean_" + Const.ARRIVAL_TIME
			    + "_perc" + percentageClassA + "_contr"
			    + contractVariation + "_user"
			    + index + ".txt";
		}
		outputDirectory = new File(percentageDIR, "EBS");
		break;
	    case Const.SJF:
		for (index = 0; index < Const.N_USERS_A; index++) {
		    fileName[index] = "SJF_ClassA_Mean_" + Const.ARRIVAL_TIME
			    + "_perc" + percentageClassA + "_user" + index
			    + ".txt";
		}
		for (; index < total; index++) {
		    fileName[index] = "SJF_ClassB_Mean_" + Const.ARRIVAL_TIME
			    + "_perc" + percentageClassA + "_user"
			    + index + ".txt";
		}
		outputDirectory = new File(percentageDIR, "SJF");
		break;
	    default:
		System.err.format("%nBad type of schedule politics.%n");
		System.exit(Const.BAD_POLITCS_ERROR);
	    }

	    if (!outputDirectory.exists())
		outputDirectory.mkdirs();

	    // Output log files
	    for (index = 0; index < total; index++) {
		writer[index] = new FileWriter(new File(outputDirectory,
			fileName[index]));
		output[index] = new PrintWriter(new BufferedWriter(
			writer[index]));
	    }

	} catch (IOException e) {
	    System.err.println("Openning streams:" + e);
	    System.exit(Const.OPENNING_STREAMS_ERROR);
	}
    }

    /**
     * Closes I/O streams.
     * */
    void closeStreams() {
	try {
	    for (int index = 0; index < (Const.N_USERS_A + Const.N_USERS_B); index++) {
		output[index].close();
		writer[index].close();
	    }
	} catch (IOException e) {
	    System.err.println("Closing files: " + e);
	    System.exit(Const.CLOSING_STREAMS_ERROR);
	}
    }

    /**
     * Configures and schedule the arrival of a new request.
     * */
    void newArrival(double time) {
	// Configures the request
	event.token.timeStamp = Sim.time() + time;
	event.token.load = Sim.expntl(Const.SERVICE_TIME, rn);
	event.token.owner = setUser();
	event.token.preemptStartTime = 0.0;
	event.token.attr[0] = ++tokenCount; // The token ID (attr[0]) MUST be different of zero

	// Configure the arrival event
	event.id = Const.ARRIVAL;
	Sim.schedule(event, time);
    }

    /**
     * @return Returns some type of class based on the CLASS_PERCENTAGE.
     * */
    User setUser() {
	return (Sim.random(0, 99, rn) < percentageClassA) ? users[Sim.random(0,
		(Const.N_USERS_A - 1), rn)] : users[Sim.random(Const.N_USERS_A,
		(Const.N_USERS_A + Const.N_USERS_B - 1), rn)];
    }

    /**
     * Handles the arrival of some request:
     *     - updates data system;
     *     - schedules the requesting to a server;
     *     - schedules the arrival of a new request.
     */
    void handleArrival() {
	Sim.update_arrivals();

	event.id = Const.RQST_SRVR;
	Sim.schedule(event, 0.0);

	newArrival(Sim.expntl(Const.ARRIVAL_TIME, rn));
    }

    /**
     * Tries to reserve a server. If got success, the ending of the request
     * exection is scheduled. Other else, the request is queued.
     */
    void requestServer() {
	if (preemptiveRequest == Const.true_) {
	    if (Sim.preempt(server, event.token, event.priority) == Const.FREE) {
		event.id = Const.RLS_SRVR;
		Sim.schedule(event, getServiceTime(event.token.load));
	    }
	}
	else
	    if (Sim.request(server, event.token, event.priority) == Const.FREE) {
		event.id = Const.RLS_SRVR;
		Sim.schedule(event, getServiceTime(event.token.load));
	    }
    }

    /**
     * Computes the service time of a request based on its load.
     * */
    static double getServiceTime(double load) {
	return (load * 1.0);
    }

    /**
     * Releases a server, updates data system and writes to log 
     * the mean service time.
     * 
     * Log: [Col_1]\t[Col_2]\t[Col_3]\t[Col_4]
     *     Col_1 - Release time;
     *     Col_2 - Current mean response time of a class;
     *     Col_3 - cRt
     *     Col_4 - Queue Length
     */
    void releaseServer() {
	event.token.owner.update(event.token.timeStamp); // Update the current mean response time
	Sim.release(server, event.token, politics);
	Sim.update_completions(event.token);

	output[event.token.owner.userId].println(Sim.time() + "\t"
		+ event.token.owner.meanResponseTime + "\t"
		+ contracts[event.token.owner.classType] + "\t"
		+ Sim.state().facility[server].queue_length);
    }
    
    /**
     * Rounds off a number for 'Const.DECIMAL_PLACE' decimal places. 
     * */
    public double rounder(double number) {
	int decimalPlace = Const.DECIMAL_PLACE;
	BigDecimal bd = new BigDecimal(number);
	bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
	number = bd.doubleValue();
	
	return number;
    }
    
    /**
     * Calculates the percentage of requests that were answerd in accordance
     * with the contract specified per user class, leading in account a
     * certain acceptable deviation.
     * */
    public void calculateFulfilledContracts() {
	int index, total = Const.N_USERS_A + Const.N_USERS_B;
	double perc;
	
	for(index = 0; index < total; index++) {
	    perc = 100.0 * ((double) users[index].satisfied / (double) users[index].requestCount);
	    fulfilledContracts[index] = rounder(perc);
	    
	    perc = 100.0 * ((double) users[index].latenciesBelowAverage / (double) users[index].requestCount);
	    latenciesBelowAverage[index] = rounder(perc);
	}
    }

    /**
     * Starts the model simulation.
     * 
     * @return Mean Residence Time for each Token.
     * */
    public double simulate() {
	
	System.out.println("Starting simulation " + simulNumber + " - (" + percentageClassA + "%A, " + contractVariation + " Contract Variation)");
	switch(politics) {
	case Const.FIFO:
	    System.out.print("FIFO...");
	    break;
	case Const.EBS:
	    System.out.print("EBS...");
	    break;
	case Const.SJF:
	    System.out.print("SJF...");
	    break;
	default:
	}

	newArrival(0.0);

	while (tokenCount <= Const.N_REQUESTS) {

	    // Gets the first element from the Future Event List
	    event = Sim.next_event(0.0, Const.ASYNC);

	    // Events handler
	    switch (event.id) {
	    case Const.ARRIVAL:
		handleArrival();
		break;

	    case Const.RQST_SRVR:
		requestServer();
		break;

	    case Const.RLS_SRVR:
		releaseServer();
		break;

	    default:
		System.err.format("%nBad event id.%n");
		System.exit(Const.BAD_EVENT_ERROR);
	    }
	}

	calculateFulfilledContracts();
	System.out.println("Finished!");

	int index, total = Const.N_USERS_A + Const.N_USERS_B;
	System.out
		.println("====================================================");
	System.out.println("Contract[A]: " + contracts[Const.CLASSE_A]);
	System.out.println("Contract[B]: " + contracts[Const.CLASSE_B]);
	System.out
		.println("----------------------------------------------------");
	for (index = 0; index < Const.N_USERS_A; index++) {
	    System.out
		    .println("ClassA mean service time[user"
			    + index
			    + "]: "
			    + users[index].meanResponseTime
			    + " - Satisfied Rate: "
			    + fulfilledContracts[index]
			    + "%");
	}
	for (; index < total; index++) {
	    System.out
		    .println("ClassB mean service time[user"
			    + (index - Const.N_USERS_A)
			    + "]: "
			    + users[index].meanResponseTime
			    + " - Satisfied Rate: "
			    + fulfilledContracts[index]
			    + "%");
	}
	System.out
		.println("====================================================");
//	Sim.report_stats_file();
	Sim.report_stats();

	closeStreams();

	return Sim.get_mean_residence_time();
    }
}